#ifndef _Material_H__
#define _Material_H__

#include "Resource.h"
#include "IteratorWrappers.h"
#include "ColourValue.h"
#include "BlendMode.h"

namespace PVM
{
	class MaterialPtr;
	class LodStrategy;
	
	class Material : public Resource
	{
		friend class SceneManager;
		friend class MaterialManager;
		
	public:
		typedef vector<float> LodValueList;
		typedef ConstVectorIterator<LodValueList> LodValueIterator;
		
	protected:
		void applyDefaults(void);
		
		typedef vector<Technique*> Techniques;
		Techniques mTechniques;
		Techniques mSupportedTechniques;
		typedef map<unsigned short, Technique*> LodTechniques;
		typedef map<unsigned short, LodTechniques> BestTechniquesBySchemeList;
		
		BestTechniquesBySchemeList mBestTechniquesBySchemeList;
		LodValueList mUserLodValues;
		LodValueList mLodValues;
		const LodStrategy* mLodStrategy;
		bool mReceiveShadows;
		bool mTransparencyCastsShadows;
		bool mCompilationRequired;
		std::string mUnsupportedReasons;
		
		void insertSupportedTechnique(Technique* t);
		void clearBestTechniqueList(void);
		void prepareImpl(void);
		void unprepareImpl(void);
		void loadImpl(void);
		void unloadImpl(void);
		size_t calculateSize(void) const {return 0;}
		
	public:
		Material(ResourceManager* creator, const std::string& name, ResourceHandle handle,
				 const std::string& group, bool isManual = false, ManualResourceLoader* loader = 0);
		~Material();
		
		MaterialPtr& operator = (const Material& rhs);
		bool isTransparent(void) const;
		void setReceiveShadows(bool enabled) {mReceiveShadows = enabled;}
		bool getReceiveShadows(void) const {return mReceiveShadows;}
		
		void setTransparencyCastsShadows(bool enabled) {mTransparencyCastsShadows = enabled;}
		bool getTransparencyCastsShadows(void) const {return mTransparencyCastsShadows;}
		
		Technique* createTechnique(void);
		Technique* getTechnique(unsigned short index);
		Technique* getTechnique(const std::string& name);
		unsigned short getNumTechniques(void) const;
		void removeTechnique(unsigned short index);
		void removeAllTechniques(void);
		typedef VectorIterator<Techniques> TechniqueIterator;
		TechniqueIterator getTechniqueIterator(void);
		
		TechniqueIterator getSupportedTechniqueIterator(void);
		Technique* getSupportedTechnique(unsigned short index);
		unsigned short getNumSupportedTechniques(void) const;
		const std::string& getUnsupportedTechniquesExplanation() const {return mUnsupportedReasons;}
		
		unsigned short getNumLodLevels(unsigned short schemeIndex) const;
		unsigned short getNumLodLevels(const std::string& schemeName) const;
		
		Technique* getBestTechnique(unsigned short lodIndex = 0, const Renderable* rend = 0);
		MaterialPtr clone(const std::string& newName, bool changeGroup = false,
						  const std::string& newGroup = StringUtil::BLANK) const;
		
		void copyDetailsTo(MaterialPtr& mat) const;
		
		void compile(bool autoManageTextureUnits = true);
		
		void setPointSize(float ps);
		void setAmbient(float red, float green, float blue);
		void setAmbient(const ColourValue& ambient);
		void setDiffuse(float red, float green, float blue, float alpha);
		void setDiffuse(const ColourValue& diffuse);
		void setSpecular(float red, float green, float blue, float alpha);
		void setSpecular(const ColourValue& specular);
		void setShininess(float val);
		void setSelfIllumination(float red, float green, float blue);
		void setSelfIllumination(const ColourValue& selfIllum);
		void setDepthCheckEnabled(bool enabled);
		void setDepthWriteEnabled(bool enabled);
		void setDepthFunction(CompareFunction func);
		void setColourWriteEnabled(bool enabled);
		void setCullingMode(CullingMode mode);
		void setManualCullingMode(ManualCullingMode mode);
		void setLightingEnabled(bool enabled);
		void setShadingMode(ShadeOptions mode);
		void setFog(
			bool overrideScene,
			FogMode mode = FOG_NONE,
					const ColourValue& colour = ColourValue::White,
					float expDensity = 0.001, float linearStart = 0.0, float linearEnd = 1.0);
		void setDepthBias(float constantBias, float slopeScaleBias);
		void setTextureFiltering(TextureFilterOptions filterType);
		void setTextureAnisotropy(int maxAniso);
		void setSceneBlending(const SceneBlendType sbt);
		void setSeparateSceneBlending(const SceneBlendType sbt, const SceneBlendType sbta);
		void setSceneBlending(const SceneBlendFactor sourceFactor, const SceneBlendFactor destFactor);
		void setSeparateSceneBlending(const SceneBlendFactor sourceFactor, const SceneBlendFactor destFactor, const SceneBlendFactor sourceFactorAlpha, const SceneBlendFactor destFactorAlpha);
		void _notifyNeedsRecompile(void);
		void setLodLevels(const LodValueList& lodValues);
		LodValueIterator getLodValueIterator(void) const;
		LodValueIterator getUserLodValueIterator(void) const;
		ushort getLodIndex(float value) const;
		const LodStrategy* getLodStrategy() const;
		void setLodStrategy(LodStrategy* lodStrategy);
		
		void touch(void)
		{
			if (mCompilationRequired)
				compile();
			Resource::touch();
		}
		
		bool applyTextureAliases(const AliasTextureNamePairList& aliasList, const bool apply = true) const;
		bool getCompilationRequired() const
		{
			return mCompilationRequired;
		}
	};
	
	class MaterialPtr : public SharedPtr<Material>
	{
	public:
		MaterialPtr() : SharedPtr<Material>() {}
		explicit MaterialPtr(Material* rep) : SharedPtr<Material>(rep) {}
		MaterialPtr(const MaterialPtr& r) : SharedPtr<Material>(r) {}
		MaterialPtr(const ResourcePtr& r) : SharedPtr<Material>()
		{
			PVM_MUTEX_CONDITIONAL(r.PVM_AUTO_MUTEX_NAME)
			{
				PVM_LOCK_MUTEX(*r.PVM_AUTO_MUTEX_NAME)
				PVM_COPY_AUTO_SHARED_MUTEX(r.PVM_AUTO_MUTEX_NAME)
				pRep = static_cast<Material*>(r.getPointer());
				pUseCount = r.useCountPointer();
				if (pUseCount)
				{
					++(*pUseCount);
				}
			}
		}
		
		MaterialPtr& operator=(const ResourcePtr& r)
		{
			if (pRep == static_cast<Material*>(r.getPointer()))
				return *this;
			release();
			
			PVM_MUTEX_CONDITIONAL(r.PVM_AUTO_MUTEX_NAME)
			{
				PVM_LOCK_MUTEX(*r.PVM_AUTO_MUTEX_NAME)
				PVM_COPY_AUTO_SHARED_MUTEX(r.PVM_AUTO_MUTEX_NAME)
				pRep = static_cast<Material*>(r.getPointer());
				pUseCount = r.useCountPointer();
				if (pUseCount)
				{
					++(*pUseCount);
				}
			}
			else
			{
				assert(r.isNull() && "RHS must be null if it has no mutex!");
				setNull();
			}
			
			return *this;
		}
	};
}

#endif
